using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using ErkerTech.DeltaScope.Engine;

namespace ErkerTech.DeltaScope.View.Models
{
    public class SwirlBlock
    {
        #region Class Object

        public static StringFormat StringFormat; 

        static SwirlBlock()
        {
            TextDisplayBuffer = 1;
            StringFormat = new StringFormat(StringFormat.GenericDefault)
                                {
                                    Trimming = StringTrimming.Word,
                                    LineAlignment = StringAlignment.Near,
                                    Alignment = StringAlignment.Near
                                };
        }

        public static int TextDisplayBuffer { get; private set; }

        public static bool WordWrap
        {
            get { return (StringFormat.FormatFlags == StringFormatFlags.NoWrap); }
            set
            {
                StringFormat = new StringFormat(StringFormat.GenericDefault)
                                    {
                                        LineAlignment = StringAlignment.Near,
                                        Alignment = StringAlignment.Near
                                    };

                if (!value)
                {
                    StringFormat.FormatFlags = StringFormatFlags.NoWrap;
                    StringFormat.Trimming = StringTrimming.Character;
                }
                else
                    StringFormat.Trimming = StringTrimming.Word;   
            }
        }
        #endregion

        #region Private Declarations
        private SolidBrush _fontBrush;
        private readonly SolidBrush _backBrush = new SolidBrush(Color.CornflowerBlue);		
        private readonly PointF[] _pointsArray = new PointF[25];
        private readonly int _textBufferSize = TextDisplayBuffer;
        #endregion
		
        #region Constructors
        public SwirlBlock(string tsLeftText, string tsRightText)
        {
            FontColor = Color.Black;
            LeftSide = new SideStructure ( tsLeftText );
            RightSide = new SideStructure ( tsRightText );
        }
        public SwirlBlock(DeltaBlock deltaBlock)
        {
            if (deltaBlock.DiffState == DeltaState.Invalid) 
                throw (new Exception("Invalid DeltaBlock State encountered."));

            FontColor = Color.Black;
            LeftSide = new SideStructure ( deltaBlock.Left );
            RightSide = new SideStructure ( deltaBlock.Right );
            EnableBiezer = (deltaBlock.DiffState != DeltaState.Match);
        }
        #endregion

        #region Properties
        public SideStructure LeftSide { get; set; }
        public SideStructure RightSide { get; set; }
        public bool EnableBiezer { get; set; }
        public Color FontColor
        {
            get {return _fontBrush.Color;}
            set {_fontBrush = new SolidBrush(value);}
        }
        public Font Font { get; private set; }
        public bool IsDiffBlock { get { return (LeftSide.Text != RightSide.Text);} }
        #endregion

        #region Private Methods
        private void DrawSwirl(Graphics toGraphics, float containerWidth)
        {
            // Declarations and initialization
            var leftTop = LeftSide.Rect.Top;
            var rightTop = RightSide.Rect.Top;
            var leftHeight = LeftSide.Rect.Height;
            var rightHeight = RightSide.Rect.Height;
			
            var leftSide = LeftSide.Rect.Width;	// Position to start drawing left side of twisted beziers from
            var rightSide = RightSide.Rect.Left;	// Position to end drawing right side of twisted beziers at
            var twistPoint = containerWidth / 2;		// Bezier control point for twist

            // Create the points that represents the 8 beziers to draw.
            // The number of point pairs is 3x+1 where x is the number of beziers.
            // The trick is that all beziers must be drawn IN ORDER and must touch.
            // This means the ending point of bezier 1 is the starting point for bezier 2.
			
            _pointsArray[00] = new PointF(1, leftTop);							// Bezier 1 starting point
            _pointsArray[01] = new PointF(1, leftTop);							// Bezier 1 starting control point
            _pointsArray[02] = new PointF(leftSide, leftTop);						// Bezier 1 ending control point
            _pointsArray[03] = new PointF(leftSide, leftTop);						// Bezier 1 ending point, Bezier 2 starting point

            _pointsArray[04] = new PointF(twistPoint, leftTop);					// Bezier 2 starting control point
            _pointsArray[05] = new PointF(twistPoint, rightTop);					// Bezier 2 ending control point
            _pointsArray[06] = new PointF(rightSide, rightTop);						// Bezier 2 ending point, Bezier 3 starting point

            _pointsArray[07] = new PointF(rightSide, rightTop);						// Bezier 3 starting control point
            _pointsArray[08] = new PointF(containerWidth, rightTop);				// Bezier 3 ending control point
            _pointsArray[09] = new PointF(containerWidth, rightTop);				// Bezier 3 ending point, Bezier 4 starting point

            _pointsArray[10] = new PointF(containerWidth, rightTop);				// Bezier 4 starting control point
            _pointsArray[11] = new PointF(containerWidth, rightTop + rightHeight);		// Bezier 4 ending control point
            _pointsArray[12] = new PointF(containerWidth, rightTop + rightHeight);		// Bezier 4 ending point, Bezier 5 starting point

            _pointsArray[13] = new PointF(containerWidth, rightTop + rightHeight);		// Bezier 5 starting control point
            _pointsArray[14] = new PointF(rightSide, rightTop + rightHeight);				// Bezier 5 ending control point
            _pointsArray[15] = new PointF(rightSide, rightTop + rightHeight);				// Bezier 5 ending point, Bezier 6 starting point

            _pointsArray[16] = new PointF(twistPoint, rightTop + rightHeight);			// Bezier 6 starting control point
            _pointsArray[17] = new PointF(twistPoint, leftTop + leftHeight);			// Bezier 6 ending control point
            _pointsArray[18] = new PointF(leftSide, leftTop + leftHeight);				// Bezier 6 ending point, Bezier 7 starting point

            _pointsArray[19] = new PointF(leftSide, leftTop + leftHeight);				// Bezier 7 starting control point
            _pointsArray[20] = new PointF(leftSide, leftTop + leftHeight);				// Bezier 7 ending control point
            _pointsArray[21] = new PointF(1, leftTop + leftHeight);					// Bezier 7 ending point, Bezier 8 starting point

            _pointsArray[22] = new PointF(1, leftTop + leftHeight);					// Bezier 8 starting control point
            _pointsArray[23] = new PointF(1, leftTop);							// Bezier 8 ending control point
            _pointsArray[24] = new PointF(1, leftTop);							// Bezier 8 ending point, Bezier 1 starting point
						
            // Create the path that holds the 4-point bezier shape
            var loPath = new GraphicsPath();
            loPath.AddBeziers(_pointsArray);

            // Draw the path to screen			
            toGraphics.FillPath( _backBrush, loPath);
            toGraphics.DrawPath(new Pen(Color.Black, 1), loPath);
        }
        private void DrawTextLine(Graphics toGraphics, LineInfo toLine, int tnLeft, int tnBlockWidth)
        {
            var loTextRect = new Rectangle(tnLeft, toLine.Top, tnBlockWidth + (TextDisplayBuffer * 2), toLine.Height);
            toGraphics.DrawString(toLine.Line, Font, _fontBrush, loTextRect, StringFormat);
        }
        #endregion

        #region Methods
        public void Calculate (Graphics toGraphics, Font toFont, int tnWidth, int tnSwirlAreaWidth, int tnLeftSideTop, int tnRightSideTop)
        {
            // Declarations
            var lnNewLeftTop = tnLeftSideTop;
            var lnNewRightTop = tnRightSideTop;
            SizeF loLineSize;

            // Either side of the swirl is the half of the width of the display area, minus the middle SwirlRegion, minus a buffer of 1 pixel...			
            var lnBlockWidth = (((tnWidth - tnSwirlAreaWidth) / 2)-1);
            Font = toFont;

            // Make sure the width of the text is already adjusted on left and right by the TextDisplayBuffer...
            lnBlockWidth -= (_textBufferSize * 2);

            //Get the Tops and Heights for the each line of text on the left side...
            for (var lnCnt = 0; lnCnt < LeftSide.ALines.Length; lnCnt++)
            {	
                loLineSize = toGraphics.MeasureString(LeftSide.ALines[lnCnt].Line, Font, lnBlockWidth + _textBufferSize, StringFormat);
                LeftSide.ALines[lnCnt].Top = lnNewLeftTop;
                LeftSide.ALines[lnCnt].Height = (int) Math.Ceiling(loLineSize.Height);
                lnNewLeftTop += LeftSide.ALines[lnCnt].Height;
            }

            //Get the Tops and Heights for the each line of text on the right side...
            for (var lnCnt = 0; lnCnt < RightSide.ALines.Length; lnCnt++)
            {
                loLineSize = toGraphics.MeasureString(RightSide.ALines[lnCnt].Line, Font, lnBlockWidth + _textBufferSize, StringFormat);
                RightSide.ALines[lnCnt].Top = lnNewRightTop;
                RightSide.ALines[lnCnt].Height = (int) Math.Ceiling(loLineSize.Height);
                lnNewRightTop += RightSide.ALines[lnCnt].Height;
            }

            // Make sure we don't have an empty rectangle on either side, so set a min-height
            if (lnNewLeftTop == tnLeftSideTop) lnNewLeftTop += (_textBufferSize * 2);
            if (lnNewRightTop == tnRightSideTop) lnNewRightTop += (_textBufferSize * 2);

            // Calculate the heights for the left and right rectangles...
            var lnLeftHeight = (lnNewLeftTop - tnLeftSideTop);
            var lnRightHeight = (lnNewRightTop - tnRightSideTop);
            
            // Build the outer rectangles
            LeftSide.Rect = new Rectangle(0, tnLeftSideTop, lnBlockWidth + (_textBufferSize * 2), lnLeftHeight);
            RightSide.Rect = new Rectangle(LeftSide.Rect.Width + tnSwirlAreaWidth, tnRightSideTop, lnBlockWidth + (_textBufferSize * 2), lnRightHeight);
        }
        public void DrawBlock(Graphics toGraphics, bool tbLVisible, bool tbRVisible)
        {
            // no need to draw if the block is above the visible range...
            if ((tbLVisible==false) && (tbRVisible==false)) return;

            var lnSwirlAreaWidth = RightSide.Rect.Left - LeftSide.Rect.Width;

            if (EnableBiezer) DrawSwirl(toGraphics, RightSide.Rect.Left + RightSide.Rect.Width );

            // Draw both sides...
            if (tbLVisible)
            {
                foreach (var loLine in LeftSide.ALines)
                    DrawTextLine(toGraphics, loLine, _textBufferSize, LeftSide.Rect.Width - _textBufferSize);
            }
            if (!tbRVisible) return;

            foreach (var loLine in RightSide.ALines)
                DrawTextLine(toGraphics, loLine, 				
                             RightSide.Rect.Width + lnSwirlAreaWidth + _textBufferSize, 
                             RightSide.Rect.Width - _textBufferSize);
        }
        #endregion
    }
}